import com.drew.imaging.FileType;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

/**
 * @author long.hua
 * @version 1.0.0
 * @since 2017-02-02 18:02
 */
public class Main {


    private final static int STAT_SUCCESS_FLAG = 1;
    private final static int STAT_REPEAT_FLAG = 0;
    private final static int STAT_FAILED_FLAG = -1;


    private static List<File> getDirs(String dirPath){
        File sourceDir = new File(dirPath);
        if (!sourceDir.exists()) {
            Log.info("目录不存在：" + dirPath);
            return Collections.emptyList();
        }

        List<File> dirList = new ArrayList<>();
        dirList.add(sourceDir);

        for (int i = 0; i < dirList.size(); i++) {
            File[] files = dirList.get(i).listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        dirList.add(file);
                    }
                }
            }
        }

        Log.info("“"+dirPath+"”中共有目录数：" + dirList.size());

        return dirList;
    }

    private static List<File> getFiles(String dirPath) {
        List<File> fileList = new ArrayList<>();

        List<File> dirList = getDirs(dirPath);

        int idx = 0;
        for (File dir : dirList) {
            if (idx++ % 10 == 0) {
                Log.info("查找目录进度：" + idx);
            }

            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (!file.isDirectory()) {
                        fileList.add(file);
                    }
                }
            }
        }

        Log.info("共有文件数：" + fileList.size());
        return fileList;
    }

    private static SimpleDateFormat yMdHms = new SimpleDateFormat("yyyyMMddHHmmss");

    private static int moveFile(File sourceFile, String targetDirPath) throws Exception {
        if (!sourceFile.exists()) {
            Log.info("源文件不存在：" + sourceFile);
            return STAT_FAILED_FLAG;
        }

        String fileCreateTime = getFileCreateTime(sourceFile);
        if (fileCreateTime == null) {
            Log.info("获取源文件创建时间失败：" + sourceFile);
            return STAT_FAILED_FLAG;
        }

        // 创建子目录
        String subTargetDirPath = getSubTargetDirPath(targetDirPath, fileCreateTime.substring(0, 6));

        // 计算新文件路径
        String sourceFileName = sourceFile.getName();

        String targetFileName = sourceFile.getName();
        String targetFilePath = composePath(subTargetDirPath, targetFileName);

        File targetFile = new File(targetFilePath);
        if (targetFile.exists()) {
            if (isSame(sourceFile, targetFile)) {
                Log.info("目标目录中存在源文件，忽略移动：" + sourceFile + " 目标文件：" + targetFile);
                return STAT_REPEAT_FLAG;

            } else {
                // 计算新文件名称
                targetFileName = getNewTargetFileName(sourceFileName, fileCreateTime);

                // 新文件是否存在
                targetFile = new File(targetFilePath);
                if (targetFile.exists()) {
                    // 同时存在相同最后修改时间的文件概率太小，忽略掉
                    Log.info("目标目录中存在源文件，忽略移动：" + sourceFile + " 目标文件：" + targetFile);
                    return STAT_REPEAT_FLAG;
                }

                // 新文件不存在时
                targetFilePath = composePath(subTargetDirPath, targetFileName);
            }
        }

        if (copyFile(sourceFile.getCanonicalPath(), targetFilePath, fileCreateTime)) {
            return STAT_SUCCESS_FLAG;
        }

        return STAT_FAILED_FLAG;
    }

    private static String getNewTargetFileName(String sourceFileName, String yMdHms) {
        int idx = sourceFileName.lastIndexOf(".");
        if (idx > 0 && idx < sourceFileName.length() - 1) {

            String suffix = sourceFileName.substring(idx + 1);
            String prefix = sourceFileName.substring(0, idx);
            return prefix + "-" + yMdHms + "." + suffix;

        } else {
            return sourceFileName + "-" + yMdHms;
        }
    }

    private static boolean isSame(File file1, File file2) {
        if (file1.exists() && file2.exists()) {
            if (file1.length() == file2.length()) {

                String file1Md5 = md5(file1);
                String file2Md5 = md5(file2);
                if (file1Md5 != null && file1Md5.equals(file2Md5)) {
                    return true;
                }
            }
        }
        return false;
    }

    // 计算文件的 MD5 值
    private static String md5(File file) {
        int len;
        byte buffer[] = new byte[1024];
        FileInputStream in = null;

        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            in = new FileInputStream(file);
            while ((len = in.read(buffer)) != -1) {
                digest.update(buffer, 0, len);
            }

            BigInteger bigInt = new BigInteger(1, digest.digest());
            return bigInt.toString(16);

        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    private static String composePath(String path1, String path2) {
        path1 = path1.replace("\\", "/");
        if (path1.lastIndexOf("/") == path1.length() - 1) {
            path1 = path1.substring(0, path1.length() - 1);
        }

        path2 = path2.replace("\\", "/");
        if (path2.indexOf("/") == 0) {
            path2 += path2.substring(1);
        }

        return path1 + "/" + path2;
    }

    private static String getSubTargetDirPath(String targetDirPath, String subTargetDirName) throws IOException {
        String subTargetDirPath = composePath(targetDirPath, subTargetDirName);
        File subTargetDir = new File(subTargetDirPath);

        // 子目录存在
        if (subTargetDir.exists()) {
            return subTargetDirPath;
        }

        // 已改名的子目录存在
        // 因为可能会对目标文件夹增加主题表明当月的照片是因啥事件而拍摄的；
        // 文件夹命名规则是：yyyyMMdd 或 yyyyMMdd-主题，这两类视为相同目录。
        List<File> subDirs = getDirs(targetDirPath);
        for (File subDir : subDirs) {
            if (subDir.getName().startsWith(subTargetDirName + "-")
                    || subDir.getName().startsWith(subTargetDirName + "—")
                    || subDir.getName().startsWith(subTargetDirName + "_")) {
                return subDir.getCanonicalPath();
            }
        }

        // 子目录不存在
        if (!subTargetDir.mkdirs()) {
            throw new IOException("创建目录失败：" + subTargetDir);
        }

        // 子目录路径
        return subTargetDirPath;
    }

    private static boolean copyFile(String oldPath, String newPath, String createTime) {
        try {

            File oldFile = new File(oldPath);
            File newFile = new File(newPath);

            InputStream fis = new FileInputStream(oldPath);
            FileOutputStream fos = new FileOutputStream(newFile);

            int len = 0;
            byte[] buf = new byte[1024];
            while ((len = fis.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            fos.close();
            fis.close();

            if (!newFile.exists()) {
                Log.info("复制失败，文件：" + oldFile.getName());

            } else {
                if (createTime != null) {
                    copyFileAttrs(newFile, createTime);
                }

                Log.info("复制成功，文件：" + oldFile.getName() + " 文件大小：" + oldFile.length() + " 字节，目标目录是：" + newFile.getParentFile().getCanonicalPath());
                return true;
            }

        } catch (Exception e) {
            Log.info("复制文件出错：" + oldPath, e);
        }

        return false;
    }

    private static void copyFileAttrs(File destFile, String createTime) {
        try {

            FileTime ft = FileTime.fromMillis(yMdHms.parse(createTime).getTime());
            Files.setAttribute(destFile.toPath(), "basic:creationTime", ft);
            Files.setAttribute(destFile.toPath(), "basic:lastModifiedTime", ft);

        } catch (Exception e) {
            Log.info("设置创建时间失败，文件：" + destFile.getName());
        }
    }

    private static SimpleDateFormat CUSTOM_DATE_FORMATS[] = new SimpleDateFormat[]{
            new SimpleDateFormat("yyyy:MM:dd HH:mm:ss")
    };

    private static long parseTime(String strDate) {
        Date date = null;

        if (strDate != null) {
            for (SimpleDateFormat simpleDateFormat : CUSTOM_DATE_FORMATS) {
                try {
                    date = simpleDateFormat.parse(strDate);
                } catch (ParseException e) {
                }
            }
        }

        if (date == null) {
            Log.info("获取日期格式异常：" + strDate);
        } else {
            return date.getTime();
        }

        return 0;
    }


    private static long getImgTime(File imgFile) {
        boolean flag = false;
        for (FileType fileType : FileType.values()) {
            if (fileType == FileType.Jpeg) {
                if (imgFile.getName().toLowerCase().endsWith(".jpg")) {
                    flag = true;
                    break;
                }
            }
            if (imgFile.getName().toLowerCase().endsWith("." + fileType.name().toLowerCase())) {
                flag = true;
                break;
            }
        }

        if (flag) {
            String DATE_TIME = "Date/Time";
            String DATE_TIME_ORIGINAL = "Date/Time Original";
            String DATE_TIME_DIGITIZED = "Date/Time Digitized";

            try {
                Metadata metadata = ImageMetadataReader.readMetadata(imgFile);
                for (Directory directory : metadata.getDirectories()) {
                    for (Tag tag : directory.getTags()) {
                        if (tag.getTagName().equalsIgnoreCase(DATE_TIME)) {
                            return parseTime(tag.getDescription());
                        } else if (tag.getTagName().equalsIgnoreCase(DATE_TIME_ORIGINAL)) {
                            return parseTime(tag.getDescription());
                        } else if (tag.getTagName().equalsIgnoreCase(DATE_TIME_DIGITIZED)) {
                            return parseTime(tag.getDescription());
                        }
                    }
                }
            } catch (ImageProcessingException | IOException e) {
                Log.info("获取照片或视频拍摄时间异常：" + imgFile.getName(), e);
            }
        }

        return 0;
    }

    // 获取文件的创建时间
    private static String getFileCreateTime(File file) {
        try {
            BasicFileAttributes ra = Files.readAttributes(file.toPath(), BasicFileAttributes.class);

            FileTime ft = ra.creationTime();
            long createTime = 0;
            if (ft != null) {
                createTime = ft.toMillis();
            }

            ft = ra.lastModifiedTime();
            long modifyTime = 0;
            if (ft != null) {
                modifyTime = ft.toMillis();
            }

            long imageTime = getImgTime(file);

            long min = 0;
            if (createTime < 1 && imageTime > 0) {
                min = Math.min(modifyTime, imageTime);

            } else if (createTime > 0 && imageTime < 1) {
                min = Math.min(createTime, modifyTime);

            } else {
                min = Math.min(Math.min(createTime, modifyTime), imageTime);
            }

            return yMdHms.format(new Date(min));

        } catch (IOException e) {
            Log.info("获取文件创建时间异常：" + file.getName(), e);
        }

        return null;
    }


    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        while (true) {

            Log.info("请输入：0（退出程序），1（开始工作）：");
            String line = scan.nextLine();
            if (!"1".equals(line)) {
                Log.info("程序退出。。。");
                break;
            }

            Log.info("请输入照片或视频来源目录：");
            String sourceDirPath = scan.nextLine();

            Log.info("请输入照片或视频存放目录：");
            String targetDirPath = scan.nextLine();

            Log.info("\n开始读取文件。。。");
            List<File> sourceFiles = getFiles(sourceDirPath);
            Log.info("读取文件完成。。。\n");

            int c = 0; // 总数
            int s = 0; // 成功
            int f = 0; // 失败
            int r = 0; // 重复
            Log.info("开始移动文件。。。");

            for (File sourceFile : sourceFiles) {
                if (c % 10 == 0) {
                    Log.info("移动进度是：" + c);
                }
                c++;
                try {
                    int flag = moveFile(sourceFile, targetDirPath);
                    if (flag == STAT_SUCCESS_FLAG) {
                        s++;
                    } else if (flag == STAT_FAILED_FLAG) {
                        f++;
                    } else {
                        r++;
                    }
                } catch (Exception e) {
                    Log.info("移动文件失败，文件：" + sourceFile, e);
                }
            }
            Log.info("移动文件完成，共：" + c + " 个，成功：" + s + "个，失败：" + f + "个，重复：" + r + "个。。。\n");
        }
    }
}
